iT邦幫忙

2024 iThome 鐵人賽

DAY 4
1
Security

資安這條路:系統化學習網站安全與網站滲透測試系列 第 4

資安這條路:Day 4 深入理解 HTTP 協定中的 CRLF 與漏洞實作

  • 分享至 

  • xImage
  •  

HTTP 協定中的 CRLF

前三篇介紹了網站架構、環境建立、域名系統等基礎知識。這次我們將深入探討 HTTP 協定中的 CRLF,理解其工作原理以及與網站安全之間的關係。

複習

前後端之間需要透過請求封包和回應封包進行溝通,中間透過 HTTP 協定,請求封包和回應封包就是 HTTP 請求封包與 HTTP 回應封包。

實作: 如何透過 curl 指令模擬瀏覽器送出請求封包與回應封包

curl nodelab.feifei.tw

  • 使用 Windows 示範
    image

  • 使用 Linux 示範
    image

  • 思考: 為什麼沒有前面所介紹的請求封包和回應封包的標頭?

    • 因為 curl 指令預設只會顯示回應的 Body 部分,而不會顯示標頭。
    • 想要查看標頭需要加上 -i 參數。

實作: 如何透過 curl 指令查看標頭

curl -i nodelab.feifei.tw

  • 使用 Windows 示範
    image

  • 使用 Linux 示範
    image

  • 思考: 加上 -i 參數可以看到回應封包的標頭,還有其他參數嗎?

    • curl 有非常多的參數
      • 可以使用 curl --help 查看所有參數的說明

實作: 查看 curl 的參數內容

curl --help

  • 使用 Windows 示範
    image
  • 使用 Linux 示範
  • 思考: 從參數裡面,猜測印出 verbose 可能會有資訊
    • -v 參數可以顯示更詳細的請求和回應過程
      • 包括 DNS 查詢、連線建立、請求標頭、回應標頭等

實作: 查看 curl 送出的細節

curl -v nodelab.feifei.tw

  • 使用 -v 查看過程

    1. 針對 nodelab.feifei.tw 進行 DNS 查詢 IP
    2. 連線到 IP 並且送出 GET 請求
      • 觀察到 User-Agent 有 curl 的版本
    3. 查看回應封包的內容(標頭與 Body)
  • 使用 Windows 示範
    image

  • 使用 Linux 示範
    image

可以發現 Linux 參數更多

  • 思考:觀察,為什麼請求封包跟回應封包都會多一個空格
    • CRLF 且分隔標頭與 Body

思考: 請求封包與回應封包

使用 curl -v nodelab.feifei.tw 指令
觀察 HTTP 請求和回應的詳細過程

  1. DNS 解析: * Host nodelab.feifei.tw:80 was resolved.
    • 這一行表示 curl 首先需要將域名 nodelab.feifei.tw 解析成對應的 IP 位址,才能與伺服器建立連線。
    • 解析過程會先查詢本地 DNS 快取,如果沒有找到,就會向 DNS 伺服器發送請求。
    • 從輸出結果 * IPv4: 172.104.100.165 可以看到,nodelab.feifei.tw 的 IP 位址為 172.104.100.165
  2. 建立連線: * Trying 172.104.100.165:80...* Connected to nodelab.feifei.tw (172.104.100.165) port 80
    • 在解析到 IP 位址後,curl 會嘗試與伺服器建立 TCP 連線。
    • :80 代表使用的連接埠是 HTTP 的預設連接埠,表示這次是 HTTP 請求,而非 HTTPS。
  3. 發送請求: > GET / HTTP/1.1> Host: nodelab.feifei.tw> User-Agent: curl/8.7.1> Accept: */*>
    • 成功建立連線後,curl 會發送 HTTP 請求給伺服器。請求封包由多個標頭組成,每個標頭佔一行,以 CRLF 結尾。
    • > 符號表示這些是使用者端發送的封包
    • GET / HTTP/1.1 表示這是一個 GET 請求,請求取得根目錄 / 的資源,使用 HTTP/1.1 協定。
    • Host: nodelab.feifei.tw 指定了請求的目標主機。
    • User-Agent: curl/8.7.1 提供使用者端資訊,這裡顯示 curl 的版本。
    • Accept: */* 表示使用者端可以接受任何類型的回應內容。
  4. 請求發送完畢: * Request completely sent off
    • curl 確認請求封包已完整發送到伺服器。
  5. 接收回應: < HTTP/1.1 200 OK< Server: nginx/1.18.0 (Ubuntu)< Date: Wed, 18 Sep 2024 04:46:30 GMT
    • 伺服器收到請求後,會處理請求並發送回應封包。回應封包也由多個標頭組成,每個標頭佔一行,以 CRLF 結尾。
    • < 符號表示這些是伺服器回覆的封包。
    • HTTP/1.1 200 OK 表示請求成功。
    • Server: nginx/1.18.0 (Ubuntu) 顯示伺服器使用的軟體和作業系統版本資訊。
    • 其他標頭如 Date, Content-Type, Content-Length, Connection, X-Powered-By, Accept-Ranges, Cache-Control, Last-Modified, ETag 則提供了回應內容的相關資訊。
  6. 回應內容: <!DOCTYPE html>
    • 這是伺服器回傳的 HTML 文件的開頭部分,後續內容包含其他 HTML 標籤等。

CRLF 介紹

CRLF 在 HTTP 通訊中扮演著重要的角色

根據 curl -v nodelab.feifei.tw 指令輸出結果的分析

CRLF回車鍵 (CR, ASCII 13, \r)換行符 (LF, ASCII 10, \n) 的組合,用於表示一行文字的結束。

▲ 歷史故事:以前打字機換行會有時間差,可能會造成傳過來的文字漏掉,因此發明兩個鍵,一「回車」,告知打字機將列印標頭定位於左邊的邊界;二「換行」,告知打字機將紙往下移動一行。

▲ 後來電腦發明之後,覺得結尾加上回車跟換行浪費空間,因此不同的作業系統有不同做法: Winodws 就是 \r\n (回車+換行),而 Linux 是只有 \n (換行),舊版 Mac OS: \r (回車)(現代 macOS 使用 \n(換行) 。

▲ 目前的編輯器多半能進行格式轉換。

HTTP 與 CRLF

在 HTTP 協定中,CRLF 用於分隔訊息的標頭 (Header) 和主體 (Body),以及標頭中的各個欄位。每個標頭欄位都以 CRLF 結尾,而標頭與主體之間使用兩個 CRLF 分隔。

以下是一個 HTTP 請求封包的範例:

GET / HTTP/1.1\r\n
Host: www.example.com\r\n
User-Agent: Mozilla/5.0\r\n
\r\n
This is the message body.
  • 請求方法 (GET)、請求路徑 (/) 和 HTTP 版本 (HTTP/1.1) 位於第一行,以 CRLF 結束。
  • 接下來是三個標頭欄位:HostUser-AgentAccept,每個欄位都以 CRLF 結束。
  • 標頭和主體之間使用兩個 CRLF 分隔。
  • 訊息主體 (Body) 中可以包含任意資料,不一定要以 CRLF 結尾。

實作: 使用 curl 觀察 CRLF

  • curl --trace - nodelab.feifei.tw

    • --trace 生成二進位的追蹤日誌
    • - 將追蹤日誌進行標準輸出(stdout),而不是寫入檔案
  • 回車鍵 (CR, ASCII 13, \r)

    • 10 進位 13
    • 16 進位 0D
  • 換行符 (LF, ASCII 10, \n)

    • 10 進位 10
    • 16 進位 0A
  • Linux 示範
    image

  • Windows 示範
    image

實作一:CRLF Injection

分析

這是什麼漏洞

這段程式碼存在一個CRLF(Carriage Return Line Feed)注入漏洞。CRLF 注入是一種攻擊者可以利用它在 HTTP 回應中插入惡意的標頭或內容的漏洞。

為什麼會有這個漏洞

漏洞的產生是因為在處理使用者提供的 url 參數時,未對其內容進行適當的驗證或過濾。

程式碼直接將使用者輸入的 url 值放入 HTTP 回應的標頭中,這使得攻擊者可以透過惡意的輸入(包含 0D 0A) 來操縱 HTTP 回應。

漏洞成因

  • 缺乏輸入驗證:未對 url 參數進行任何形式的檢查,直接使用。
  • 直接插入 HTTP 標頭:使用者輸入被直接插入到 Location 標頭中,沒有過濾特殊字符,如 CR(\r)和 LF(\n)。
  • 未考慮特殊字符的影響:HTTP 協定中,CRLF 被用來分隔標頭和主體,攻擊者可以利用這一點插入新的標頭或修改回應內容。

漏洞影響:

  • HTTP 回應拆分攻擊:攻擊者可以插入額外的 HTTP 標頭或修改回應主體,可能導致 XSS(跨站腳本攻擊)或其他惡意行為。
  • Cookie 操縱:透過注入 Set-Cookie 標頭,攻擊者可以設定或修改使用者的 Cookie,可能導致會話劫持。
  • 釣魚和重新導向攻擊:攻擊者可以將使用者重新導向到惡意網站,進行釣魚或散播惡意軟體。

CRLF Injection

可參考 Perl 版本,可造成 XSS 與 Session 固定攻擊:
https://ithelp.ithome.com.tw/articles/10242682

本次 Lab 實作

  • 更新 Server.js
// 設定重新導向的路由
app.get('/redirect', (req, res) => {
  const url = req.query.url;
  res.writeHead(302, { 'Location': url });
  res.end();
});

重新啟動 Docker Compose

使用 docker-compose up -d --build

進行演練:正常情境

  1. 輸入 url 參數
    http://nodelab.feifei.tw/redirect?url=https://feifei.tw
  2. 觀察開發者工具正常跳轉

進行演練:惡意內容

  1. 輸入 url 參數
    http://nodelab.feifei.tw/redirect?url=%0d%0aSet-cookie:feifei%3Dgood
  2. 出現錯誤訊息
    image
  3. 分析錯誤訊息
    • TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["Location"]
      • 這個錯誤代表在設定 "Location" 標頭時,檢測到了無效文字

分析惡意內容

  • 進行攻擊嘗試
    • 請求範例:/redirect?url=/%0D%0ASet-Cookie:feifei=good
    • CRLF 注入嘗試%0D%0A 是 URL 編碼的 CR(\r)和 LF(\n),代表換行。
    • 攻擊者目標:試圖在 HTTP 回應中插入一個新的 Set-Cookie 標頭,以設定名為 feifei、值為 good 的 Cookie。
  • Node.js 的保護機制
    • 內建的安全檢查:Node.js 的 http 模組會自動檢查並阻止在 HTTP 標頭中插入 CR 和 LF 等非法字符。
    • 錯誤處理:如果檢測到非法字符,Node.js 會拋出一個錯誤,例如 Error [ERR_INVALID_CHAR]: Invalid character in header content ["Location"]
    • 防止攻擊成功:這種內建機制可以防止 CRLF 注入攻擊,即使開發者未對輸入進行驗證。

實作二:開放式重新導向漏洞(Open Redirect)

開放式重新導向漏洞

這是什麼漏洞

這段程式碼存在一個開放式重新導向漏洞(Open Redirect)。攻擊者可以利用該漏洞,將使用者從您信任的網站重新導向到惡意網站,進而可能進行釣魚攻擊或散播惡意軟體。

為什麼會有這個漏洞

漏洞的產生是因為在處理使用者提供的 url 參數時,未對其內容進行適當的驗證或限制。程式碼直接使用了使用者輸入的 url 值進行重新導向,這使得攻擊者可以指定任何網址,甚至是惡意網站。

漏洞成因

  • 缺乏輸入驗證:未對 url 參數進行任何形式的檢查或限制,直接使用。
  • 信任使用者輸入:假設使用者會提供合法且安全的網址,忽略了可能的惡意輸入。
  • 缺少域名限制:未限制重新導向的目標域名,允許重新導向到任何網站。

漏洞影響

  • 釣魚攻擊:攻擊者可以構造一個看似合法的連結,實際上將使用者重新導向到釣魚網站,竊取敏感資訊。
  • 惡意軟體散播:使用者可能被引導至含有惡意程式的網站,導致裝置被感染。
  • 信任被濫用:使用者信任網站,但卻被重新導向到不安全的第三方網站,損害網站聲譽。

進行演練:惡意內容

  1. 輸入惡意的 url 參數:

    http://nodelab.feifei.tw/redirect?url=http://malicious-site.com
    
  2. 使用者將被重新導向到攻擊者控制的網站。

修復方式

  1. 輸入驗證和過濾:對 url 參數進行嚴格的驗證和過濾,移除或轉義特殊字符。

    app.get('/redirect', (req, res) => {
      let url = req.query.url;
      if (url && !/[\r\n]/.test(url)) {
        res.writeHead(302, { 'Location': url });
        res.end();
      } else {
        res.status(400).send('無效的重新導向網址');
      }
    });
    
  2. 使用內建的重新導向方法:使用框架提供的 res.redirect() 方法,這個方法會自動處理特殊字符並設定適當的標頭。

    app.get('/redirect', (req, res) => {
      const url = req.query.url;
      res.redirect(302, url);
    });
    
  3. 實施白名單策略:限制可以重新導向的 URL,僅允許預先定義的安全網址或相對路徑。

    const allowedUrls = ['/home', '/profile', '/settings'];
    
    app.get('/redirect', (req, res) => {
      const url = req.query.url;
      if (allowedUrls.includes(url)) {
        res.redirect(302, url);
      } else {
        res.status(400).send('無效的重新導向網址');
      }
    });
    
  4. URL 編碼:對輸入的 URL 進行編碼,確保特殊字符被正確處理。

    const encodeUrl = require('encodeurl');
    
    app.get('/redirect', (req, res) => {
      const url = encodeUrl(req.query.url);
      res.redirect(302, url);
    });
    
  5. 移除協定相對的 URL:防止使用者使用 //malicious-site.com 這樣的協定相對 URL。

     app.get('/redirect', (req, res) => {
       const url = req.query.url;
       if (url && !url.startsWith('//')) {
         res.redirect(302, url);
       } else {
         res.status(400).send('無效的重新導向網址');
       }
     });
    

小結

為了保障應用的安全性,開發者應該:

  • 始終對使用者輸入進行驗證和過濾,避免直接使用未經處理的輸入。
  • 利用框架和語言提供的安全功能,如使用 res.redirect() 而非手動設定標頭。
  • 瞭解常見的安全漏洞和攻擊手法,如 CRLF 注入、XSS 等,並採取預防措施。
  • 定期檢查和更新程式碼

總結

在這篇文章中,深入探討了 HTTP 協定中的 CRLF(Carriage Return Line Feed)以及其在網路安全中的重要性。

透過使用 curl 指令,實作了如何模擬瀏覽器的請求和回應,並觀察了 HTTP 封包的細節,特別是 CRLF 在分隔標頭和主體中的作用。

還探討 CRLF 注入漏洞,分析了其成因和影響,並嘗試在一個範例程式碼中進行攻擊,觀察到 Node.js 的內建安全機制如何防止此類攻擊。

研究開放式重新導向(Open Redirect)漏洞,了解攻擊者如何利用未經驗證的輸入將使用者導向惡意網站,並提供了多種修復方法,包括輸入驗證、使用內建的重導方法、實施白名單策略和 URL 編碼。

最後,總結在開發中應該始終對使用者輸入進行驗證,利用框架提供的安全功能,了解常見的安全漏洞,並定期檢查和更新程式碼以保障應用的安全性。

選擇題

題目一:在 HTTP 協定中,CRLF 的主要作用是什麼?

A) 分隔域名和路徑
B) 分隔標頭和主體,以及標頭中的各個欄位
C) 加密 HTTP 請求
D) 儲存 Cookie 資料

答案:B) 分隔標頭和主體,以及標頭中的各個欄位


題目二:CRLF 注入漏洞的主要成因是什麼?

A) 伺服器硬體故障
B) 使用者輸入未經適當驗證直接用於 HTTP 標頭
C) 網路連線不穩定
D) 加密協定不夠安全

答案:B) 使用者輸入未經適當驗證直接用於 HTTP 標頭


題目三:以下哪種方法不能有效防止開放式重新導向漏洞?

A) 對輸入的 URL 進行嚴格的驗證和過濾
B) 使用框架提供的 res.redirect() 方法
C) 實施白名單策略,限制可重導的 URL
D) 允許使用者輸入任意的重新導向 URL

答案:D) 允許使用者輸入任意的重新導向 URL


題目四:在 Node.js 中,哪一個內建機制可以防止 CRLF 注入攻擊?

A) 自動加密所有的 HTTP 請求
B) 檢查並阻止 HTTP 標頭中的非法字符
C) 強制所有連線使用 HTTPS
D) 自動更新所有的相依套件

答案:B) 檢查並阻止 HTTP 標頭中的非法字符


題目五:為了防止 CRLF 注入和開放式重新導向漏洞,開發者應該怎麼做?

A) 忽略使用者輸入,直接使用預設值
B) 將所有輸入都存入資料庫,以便日後分析
C) 對使用者輸入進行驗證和過濾,並使用安全的重新導向方法
D) 禁止所有的重新導向功能

答案:C) 對使用者輸入進行驗證和過濾,並使用安全的重新導向方法

本次實作指南

步驟一:理解 CRLF 在 HTTP 中的作用

  • CRLF(\r\n)用於在 HTTP 協定中分隔標頭和主體,以及標頭中的各個欄位。
  • 熟悉 HTTP 請求和回應的格式,有助於理解如何進行攻擊和防禦。

步驟二:使用 curl 觀察 HTTP 封包

  • 使用 curl -v 查看請求和回應的詳細資訊。
  • 使用 curl --trace - 可以觀察到 CRLF 的實際運作。

步驟三:實作可能存在漏洞的程式碼

  • server.js 中添加一個重新導向的路由,直接使用使用者的輸入:
    app.get('/redirect', (req, res) => {
      const url = req.query.url;
      res.writeHead(302, { 'Location': url });
      res.end();
    });
    
  • 重啟伺服器,測試正常的重新導向功能。

步驟四:嘗試進行 CRLF 注入攻擊

  • 構造惡意的 URL,例如:/redirect?url=%0d%0aSet-Cookie:feifei%3Dgood
  • 觀察伺服器的反應,並理解 Node.js 的內建保護機制如何防止攻擊。

步驟五:理解開放式重新導向漏洞

  • 測試將重新導向的 URL 修改為惡意網站,觀察使用者被重新導向的情況。
  • 理解這種漏洞可能帶來的風險,如釣魚攻擊和惡意軟體散播。

步驟六:修復漏洞

  • 輸入驗證和過濾:檢查並過濾使用者輸入,防止特殊字符和非法網址。
  • 使用框架的重新導向方法:改用 res.redirect(),讓框架處理安全性問題。
  • 實施白名單策略:僅允許預先定義的安全網址或相對路徑進行重新導向。
  • URL 編碼:對輸入的 URL 進行編碼,確保特殊字符被正確處理。

修正後的程式碼範例

app.get('/redirect', (req, res) => {
  const url = req.query.url;
  if (url && !url.startsWith('//') && !/[\r\n]/.test(url)) {
    res.redirect(302, url);
  } else {
    res.status(400).send('無效的重新導向網址');
  }
});

步驟七:驗證修復效果

  • 再次嘗試進行之前的攻擊,確認漏洞已被修復。
  • 測試正常的重新導向功能,確保未受到影響。

步驟八:總結與反思

  • 撰寫本次練習心得

上一篇
資安這條路:Day 3 深入探索 DNS:從基礎概念到資安風險
下一篇
資安這條路:Day5 深入探索HTTP請求與回應:安全性與身分驗證的關鍵概念
系列文
資安這條路:系統化學習網站安全與網站滲透測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言